package com.jie.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jie.constants.RedisConstants;
import com.jie.constants.RedisSessionConstants;
import com.jie.constants.RoleTypeConst;
import com.jie.dto.UserBackDTO;
import com.jie.dto.UserDetailDTO;
import com.jie.dto.UserLoginTokenDTO;
import com.jie.entity.BodyDimension;
import com.jie.entity.HealthInfo;
import com.jie.entity.UserInfo;
import com.jie.entity.UserRole;
import com.jie.enums.EmailInfoEnum;
import com.jie.exception.BizException;
import com.jie.mapper.*;
import com.jie.mp.generate.utils.PageUtils;
import com.jie.response.RespBean;
import com.jie.service.UserInfoService;
import com.jie.service.UserRoleService;
import com.jie.template.impl.AbstractEmailManage;
import com.jie.template.impl.CaptchaEmailImpl;
import com.jie.utils.IpUtils;
import com.jie.utils.JwtTokenUtil;
import com.jie.utils.UserUtils;
import com.jie.vo.*;
import com.sun.org.apache.bcel.internal.generic.NEW;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.swagger.models.auth.In;
import org.apache.catalina.User;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.jie.constants.CommonConst.*;
import static com.jie.enums.ZoneEnum.SHANGHAI;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zwq
 * @since 2022-01-04
 */
@Service
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
    /**
     * 更改用户角色
     *
     * @param userRoleVO
     */
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Value("${jwt.expiration}")
    private String expiration;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private BodyDimensionMapper bodyDimensionMapper;
    @Autowired
    private HealthInfoMapper healthInfoMapper;
    @Autowired
    private CaptchaEmailImpl captchaEmail;

    /**
     * 后台管理登录方法
     *
     * @param username
     * @param password
     * @param code
     * @param request
     * @return
     */
    @Override
    public RespBean login(String username, String password, String code, HttpServletRequest request) {
        //验证码
        String captcha = (String) request.getSession().getAttribute("captcha");
        if (StringUtils.isEmpty(captcha) || !captcha.equalsIgnoreCase(code)) {
            return RespBean.error("验证码错误,重新输入");
        }
        //这个东西其实就相当于我们的根据用户名获取用户信息，因为admin实现类UserDetails接口
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        //这里要考虑我们前端传过来的是明文密码，我们这里判断的是加密的所有要这样判断
        if (userDetails == null || !passwordEncoder.matches(password, userDetails.getPassword())) {
            return RespBean.error("用户名或密码不正确!");
        }
        if (!userDetails.isEnabled()) {
            return RespBean.error("账号被禁用，请联系管理员!");
        }
        // 将用户信息存储在Redis中
        String key = RedisSessionConstants.USER_LOGIN_INFO + username;
        redisTemplate.opsForValue().set(key, JSON.toJSONString(userDetails), Integer.parseInt(expiration), TimeUnit.SECONDS);
        //更新ip地址和ip分部
        return token(userDetails);
    }

    /**
     * 返回token
     *
     * @param userDetails
     * @return
     */
    public RespBean token(UserDetails userDetails) {
        //更新security登录对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //成功之后分配令牌
        String token = jwtTokenUtil.generateToken(userDetails);
        HashMap<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);
        return RespBean.success("登录成功", tokenMap);
    }

    public UserLoginTokenDTO tokenApp(UserDetails userDetails) {
        //更新security登录对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //成功之后分配令牌
        String token = jwtTokenUtil.generateToken(userDetails);
        return UserLoginTokenDTO.builder().token(token).tokenHead(tokenHead).build();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateUserRole(UserRoleVO userRoleVO) {
        // 更新用户角色和昵称
        UserInfo userInfo = UserInfo.builder()
                .id(userRoleVO.getUserInfoId())
                .nickname(userRoleVO.getNickname())
                .build();
        this.baseMapper.updateById(userInfo);
        // 删除用户角色重新添加
        userRoleService.remove(new LambdaQueryWrapper<UserRole>()
                .eq(UserRole::getUserId, userRoleVO.getUserInfoId()));
        List<UserRole> userRoleList = userRoleVO.getRoleIdList().stream()
                .map(roleId -> UserRole.builder()
                        .roleId(roleId)
                        .userId(userRoleVO.getUserInfoId())
                        .build())
                .collect(Collectors.toList());
        userRoleService.saveBatch(userRoleList);


    }

    /**
     * 根据登录成功后获取用户信息
     *
     * @return
     */
    @Override
    public UserDetailDTO getAdminByUserName() {
        UserDetailDTO admin = UserUtils.getLoginUser();
        updateUserInfo();
        admin.setPassword(null);
        return admin;
    }

    /**
     * 用户修改密码
     *
     * @param password
     */
    @Override
    public void updateUserPassword(String password) {
        this.baseMapper.update(new UserInfo(), new LambdaUpdateWrapper<UserInfo>()
                .set(UserInfo::getPassword, BCrypt.hashpw(password, BCrypt.gensalt()))
                .eq(UserInfo::getUsername, UserUtils.getLoginUser().getUsername()));
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED, timeout = 30)
    @Override
    public RespBean<?> register(UserRegisterVO userRegisterVO) {
        //判断redis是否有当前用户名（手机号）
        try {
            SetOperations<String, String> set = redisTemplate.opsForSet();
            if (Boolean.FALSE.equals(set.isMember(RedisConstants.USER_LIST, userRegisterVO.getUsername()))) {
                //创建用户信息
                UserInfo userInfo = new UserInfo();
                userInfo.setUsername(userRegisterVO.getUsername());
                userInfo.setPassword(BCrypt.hashpw(userRegisterVO.getPassword(), BCrypt.gensalt()));
                //默认头像
                userInfo.setAvatar(IMAGES);
                userInfo.setIsDisable(0);
                userInfo.setNickname(userRegisterVO.getNickname());
                //加入mysql 并返回id
                this.baseMapper.insertUserInfo(userInfo);
                set.add(RedisConstants.USER_LIST, userRegisterVO.getUsername());
                //因为子事务有aop 所以要采用代理对象调用方法
                ((UserInfoServiceImpl) AopContext.currentProxy()).addRoleAndHealthAndBodyDimension(userInfo.getId());
                return RespBean.success("注册成功");
            }
        } catch (Exception e) {
            log.error("用户注册失败,出现异常");
            e.printStackTrace();
        }
        return RespBean.error("账户已注册过");
    }

    /**
     * 添加角色信息和用户维度表信息和锻炼数据表信息
     *
     * @param userId
     */
    @Async(value = "taskExecutor")
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED, timeout = 30)
    public void addRoleAndHealthAndBodyDimension(Integer userId) {
        //添加角色信息
        userRoleMapper.insert(UserRole.builder().userId(userId).roleId(RoleTypeConst.USER).build());
        //添加身体维度表
        BodyDimension body = BodyDimension.builder()
                .userId(userId)
                .build();
        bodyDimensionMapper.saveBodyInfo(body);
        //添加锻炼信息表
        healthInfoMapper.insert(HealthInfo.builder().bodyDimensionId(body.getId()).userId(userId).build());
        //创建用户经验表
    }

    public PageResult<UserBackDTO> listUserBackDTO(ConditionVO userQuery) {
        userQuery.setCurrent(userQuery.getCurrent() > 0 ? userQuery.getCurrent() : 1);
        userQuery.setSize(userQuery.getSize() > 0 ? userQuery.getSize() : 10);
        // 查询后台用户数量
        Integer count = this.baseMapper.countUser(userQuery);
        if (count == 0) {
            return new PageResult<>();
        }
        // 查询后台用户列表
        List<UserBackDTO> userBackRespList = this.baseMapper.listUsers(userQuery);
        return new PageResult<>(userBackRespList, count);
    }

    @Override
    public boolean emailSendCode(QQEmailVO qqEmailSendCodeVO) {
        //模板方法生成QQ邮箱并发送验证码
        try {
            captchaEmail.sendEmail(qqEmailSendCodeVO.getToEmail());
        } catch (Exception e) {
            throw new BizException("QQ邮箱发送验证码失败");
        }
        return true;
    }

    @Override
    public boolean qqEmailRegister(QQEmailRegisterLoginVO qqEmailRegisterLoginVO) {
        return false;
    }

    @Override
    public UserLoginTokenDTO loginApp(String username, String password, HttpServletRequest request) {
        //这个东西其实就相当于我们的根据用户名获取用户信息，因为admin实现类UserDetails接口
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        //这里要考虑我们前端传过来的是明文密码，我们这里判断的是加密的所有要这样判断
        if (userDetails == null || !passwordEncoder.matches(password, userDetails.getPassword())) {
            throw new BizException("用户名或密码不正确");
        }
        if (!userDetails.isEnabled()) {
            throw new BizException("账号被禁用，请联系管理员!");
        }
        // 用户信息存入Redis
        String key = RedisSessionConstants.USER_LOGIN_INFO + username;
        redisTemplate.opsForValue().set(key, JSON.toJSONString(userDetails), Integer.parseInt(expiration), TimeUnit.SECONDS);
        //更新ip地址和ip分部
        return tokenApp(userDetails);
    }

    /**
     * 刷新Token
     */
    @Override
    public HashMap<String, String> refreshToken(String token) {
        if (StrUtil.isBlank(token)) {
            throw new BizException("token为空，请检查token是否正确");
        }
        // 截取authtoken
        String oldToken = token.substring(tokenHead.length());
        // 校验令牌合法性
        Claims claims = null;
        try {
            claims = jwtTokenUtil.parseToken(oldToken);
        } catch (Exception e) {
            throw new BizException("令牌非法");
        }
        // 刷新Redis
        String username = claims.getSubject();
        String key = RedisSessionConstants.USER_LOGIN_INFO + username;
        // 获取用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        redisTemplate.opsForValue().set(key, JSON.toJSONString(userDetails), Integer.parseInt(expiration), TimeUnit.SECONDS);
        // 刷新Token
        String newToken = jwtTokenUtil.generateToken(userDetails);
        HashMap<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", newToken);
        tokenMap.put("tokenHead", tokenHead);
        return tokenMap;
    }

    /**
     * 退出登录
     */
    @Override
    public void logout() {
        // 从Redis中删除用户信息
        String key = RedisSessionConstants.USER_LOGIN_INFO + UserUtils.getLoginUser().getUsername();
        redisTemplate.delete(key);
    }

    /**
     * 更新用户信息
     */
    @Async(value = "taskExecutor")
    public void updateUserInfo() {
        UserInfo userInfo = UserInfo.builder()
                .id(UserUtils.getLoginUser().getId())
                .ipAddress(UserUtils.getLoginUser().getIpAddress())
                .ipSource(UserUtils.getLoginUser().getIpSource())
                .lastLoginTime(UserUtils.getLoginUser().getLastLoginTime())
                .build();
        this.updateById(userInfo);
    }

}
